package com.cloudfun.campusshare.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.ConsumeSizeDTO;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.device.protocol.AliyunIotMsgSender;
import com.cloudfun.campusshare.device.protocol.command.*;
import com.cloudfun.campusshare.entity.DeviceEntity;
import com.cloudfun.campusshare.entity.DevicePropertyEntity;
import com.cloudfun.campusshare.entity.InvokeHistoryEntity;
import com.cloudfun.campusshare.service.AbstractDeviceOpService;
import com.cloudfun.campusshare.service.InvokeHistoryService;
import com.cloudfun.campusshare.service.ReplenishService;
import com.cloudfun.campusshare.util.haier.OperateParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2020/3/23
 */
@Slf4j
@Service
public class AliyunIOTDeviceOpServiceImpl extends AbstractDeviceOpService {

    @Autowired
    private AliyunIotMsgSender aliyunIotMsgSender;

    @Autowired
    private InvokeHistoryService invokeHistoryService;

    @Autowired
    private ReplenishService replenishService;

    private static final Map<String, CommandFactory> COMMAND_FACTORY_MAP = new HashMap<>();

    private static final GxyCommandFactory GXY_COMMAND_FACTORY = new GxyCommandFactory();
    private static final GgyCommandFactory GGY_COMMAND_FACTORY = new GgyCommandFactory();
    private static final BxyCommandFactory BXY_COMMAND_FACTORY = new BxyCommandFactory();
    private static final BxxCommandFactory BXX_COMMAND_FACTORY = new BxxCommandFactory();

    static {
        COMMAND_FACTORY_MAP.put(DeviceType.BXY6_5.getName(), BXY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.BXYD6_5.getName(), BXY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.BXX6_5.getName(), BXX_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.GXY8.getName(), GXY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.GXY13_5.getName(), GXY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.GGY8.getName(), GGY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.GGY6.getName(), GGY_COMMAND_FACTORY);
        COMMAND_FACTORY_MAP.put(DeviceType.GGY13_5.getName(), GGY_COMMAND_FACTORY);
    }

    @Override
    public DevicePlatform supportPlatform() {
        return DevicePlatform.ALIYUN_IOT;
    }

    @Override
    protected BaseResult<String> doGetPropertyByName(DeviceEntity deviceEntity, String propertyName) {
        DevicePropertyEntity devicePropertyEntity = devicePropertyRepo.findByDeviceIdAndPropertyName(deviceEntity.getId(), propertyName);
        if (null == devicePropertyEntity) {
            return BaseResultFactory.produceResult(Code.SUCCESS, null);
        }
        return BaseResultFactory.produceResult(Code.SUCCESS, devicePropertyEntity.getPropertyValue());
    }

    @Override
    protected BaseResult doDeviceOnline(DeviceEntity deviceEntity) {
        return BaseResultFactory.produceResult(Code.SUCCESS, false);
    }

    @Override
    public BaseResult doReset(DeviceEntity deviceEntity) {
        // 底板复位需要2秒左右的时间，上位机应该做一下处理，等复位后再发新指令或重发几次
        if(DeviceType.PUMP_LA_1.getName().equals(deviceEntity.getDeviceType())
                || DeviceType.PUMP_LA_2.getName().equals(deviceEntity.getDeviceType())){
            PumpFrame frame = PumpCommandFactory.reset();
            aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), frame);
        }else{
            // 底板复位需要2秒左右的时间，上位机应该做一下处理，等复位后再发新指令或重发几次
            Frame frame = getCommandFactory(deviceEntity.getDeviceType()).reset();
            aliyunIotMsgSender.sendAndLock(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), frame);
            // TODO 底板启动后立即复位，复位指令返回的数据不正确，需要手动查询一下
            BaseConstant.SCHEDULED_EXECUTOR.schedule(() -> {
                Frame queryFrame = getCommandFactory(deviceEntity.getDeviceType()).query();
                aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), queryFrame);
            }, 3, TimeUnit.SECONDS);
        }

        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

    private void addInvokeHistory(String operateMethod, Object param, String orderId, String respResult, Boolean success, String function, String deviceId) {
        // save invoke history
        InvokeHistoryEntity historyEntity = InvokeHistoryEntity.builder()
                .method(operateMethod)
                .param(JSONObject.toJSONString(param))
                .orderId(orderId)
                .result(respResult)
                .resultStatus(success ? "SUCCESS" : "FAILED")
                .userId(AuthenticationHolder.getUserId())
                .function(function)
                .deviceId(deviceId)
                .build();
        invokeHistoryService.create(historyEntity);
    }

    @Override
    protected BaseResult doWash(DeviceEntity deviceEntity, RollerWasherOpVO rollerWasherOpVO) {
        Frame gxyWashFrame;
        // 如果是暂停状态
        if (DeviceStatus.PAUSE.equals(deviceEntity.getStatus())) {
            gxyWashFrame = GXY_COMMAND_FACTORY.start();
        } else {
            gxyWashFrame = this.getGxyWashFrame(rollerWasherOpVO.getWashType(), rollerWasherOpVO.isAddLaundryDetergent());
        }
        boolean result = aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), rollerWasherOpVO.getImei(), gxyWashFrame);
        OperateParam param = new OperateParam();
        param.setImei(rollerWasherOpVO.getImei());
        param.setCmdName("grCommonWashCW");
        Map<String, String> cmdMap = new LinkedHashMap<>();
        cmdMap.put("laundryCycleCW", rollerWasherOpVO.getWashType());
        param.setCmdArgs(cmdMap);
        addInvokeHistory("operate", param, rollerWasherOpVO.getOrderId(), String.valueOf(result), result, "wash", deviceEntity.getId());
        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

    private Frame getGxyWashFrame(String washType, boolean addLaundryDetergent) {
        boolean heat40 = false;
        boolean heat60 = false;
        boolean quietRinseStatus = false;
        boolean strongDecontamination = false;
        switch (washType) {
//            case "1  ":break;
//            case "2  ":break;
//            case "3  ":break;
//            case "4  ":break;
//            case "5  ":break;
//            case "8  ":break;
            case "106":
                washType = "1";
                quietRinseStatus = true;
                break;
            case "107":
                washType = "2";
                quietRinseStatus = true;
                break;
            case "108":
                washType = "3";
                quietRinseStatus = true;
                break;
            case "109":
                washType = "8";
                quietRinseStatus = true;
                break;
            case "110":
                washType = "1";
                quietRinseStatus = true;
                strongDecontamination = true;
                break;
            case "111":
                washType = "2";
                quietRinseStatus = true;
                strongDecontamination = true;
                break;
            case "112":
                washType = "3";
                quietRinseStatus = true;
                strongDecontamination = true;
                break;
            case "113":
                washType = "1";
                strongDecontamination = true;
                break;
            case "114":
                washType = "2";
                strongDecontamination = true;
                break;
            case "115":
                washType = "3";
                strongDecontamination = true;
                break;
            case "116":
                washType = "1";
                heat40 = true;
                break;
            case "117":
                washType = "2";
                heat40 = true;
                break;
            case "118":
                washType = "3";
                heat40 = true;
                break;
            case "119":
                washType = "1";
                quietRinseStatus = true;
                heat40 = true;
                break;
            case "120":
                washType = "2";
                quietRinseStatus = true;
                heat40 = true;
                break;
            case "121":
                washType = "3";
                quietRinseStatus = true;
                heat40 = true;
                break;
            case "122":
                washType = "1";
                heat60 = true;
                break;
            case "123":
                washType = "2";
                heat60 = true;
                break;
            case "124":
                washType = "3";
                heat60 = true;
                break;
            case "125":
                washType = "1";
                heat60 = true;
                quietRinseStatus = true;
                break;
            case "126":
                washType = "2";
                heat60 = true;
                quietRinseStatus = true;
                break;
            case "127":
                washType = "3";
                heat60 = true;
                quietRinseStatus = true;
                break;

        }
        return GXY_COMMAND_FACTORY.washGroupCommand(washType, heat40, heat60, quietRinseStatus, strongDecontamination, addLaundryDetergent);
    }

    @Override
    public BaseResult doDrying(DeviceEntity deviceEntity, DryerOperateVO deviceDryingVO) {
        Frame frame = GGY_COMMAND_FACTORY.washGroupCommand(deviceDryingVO.getTempLevel(), deviceDryingVO.getDryTime(), deviceDryingVO.getStartEnabled(), deviceDryingVO.getWaitingTime());
        boolean result = aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), frame);
        OperateParam param = new OperateParam();
        param.setImei(deviceDryingVO.getImei());
        param.setCmdName("grCommonSetCD");
        Map<String, String> cmdMap = new LinkedHashMap<>();
        cmdMap.put("tempLevel", deviceDryingVO.getTempLevel() == null ? null : deviceDryingVO.getTempLevel().toString());
        cmdMap.put("waitingTime", deviceDryingVO.getWaitingTime() == null ? null : deviceDryingVO.getWaitingTime().toString());
        cmdMap.put("dryTime", deviceDryingVO.getDryTime() == null ? null : deviceDryingVO.getDryTime().toString());
        cmdMap.put("startEnabled", deviceDryingVO.getStartEnabled() == null ? null : deviceDryingVO.getStartEnabled().toString());
        param.setCmdArgs(cmdMap);
        addInvokeHistory("operate", param, deviceDryingVO.getOrderId(), String.valueOf(result), result, "dry", deviceEntity.getId());
        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

    @Override
    public BaseResult doPulseWash(DeviceEntity deviceEntity, PulseWasherOperateVO operateVO) {
        Frame frame = BXY_COMMAND_FACTORY.washGroupCommand(operateVO.getWashType(), operateVO.isDisinfection(), operateVO.getPulseNum(), operateVO.isAddLaundryDetergent());
        boolean result = aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), operateVO.getImei(), frame);
        // 波轮洗衣机
        OperateParam param = new OperateParam();
        param.setImei(operateVO.getImei());
        param.setCmdName("grCommonWash");
        Map<String, String> cmdMap = new LinkedHashMap<>();
        // 洗衣程序, 1-7
        cmdMap.put("laundryCycleCW", Integer.toString(operateVO.getWashType()));
        // 消毒功能状态 true false
        cmdMap.put("disinfectionStatus", Boolean.toString(operateVO.isDisinfection()));
        //脉冲个数 0\1\2\4\8
        cmdMap.put("pulseNum", Integer.toString(operateVO.getPulseNum()));
        param.setCmdArgs(cmdMap);
        addInvokeHistory("operate", param, operateVO.getOrderId(), String.valueOf(result), result, "pulseWash", deviceEntity.getId());
        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

    @Override
    public BaseResult doShoeWash(DeviceEntity deviceEntity, ShoeWasherOperateVO operateVO) {
        Frame frame = BXX_COMMAND_FACTORY.washGroupCommand(operateVO.getWashType(), operateVO.isDisinfection(), operateVO.getPulseNum(), operateVO.isAddLaundryDetergent());
        boolean result = aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), operateVO.getImei(), frame);
        // 波轮洗衣机
        OperateParam param = new OperateParam();
        param.setImei(operateVO.getImei());
        param.setCmdName("grCommonWash");
        Map<String, String> cmdMap = new LinkedHashMap<>();
        // 洗衣程序, 1-7
        cmdMap.put("laundryCycleCW", Integer.toString(operateVO.getWashType()));
        // 消毒功能状态 true false
        cmdMap.put("disinfectionStatus", Boolean.toString(operateVO.isDisinfection()));
        //脉冲个数 0\1\2\4\8
        cmdMap.put("pulseNum", Integer.toString(operateVO.getPulseNum()));

        param.setCmdArgs(cmdMap);
        addInvokeHistory("operate", param, operateVO.getOrderId(), String.valueOf(result), result, "shoeWash", deviceEntity.getId());

        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

    @Override
    public BaseResult doSendDeviceVerifyCode(DeviceEntity deviceEntity, String verifyCode) {
        Frame frame = getCommandFactory(deviceEntity.getDeviceType()).verifyCode(verifyCode);
        aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), frame);
        return BaseResultFactory.produceEmptyResult(Code.SUCCESS);
    }

    private CommandFactory getCommandFactory(String deviceType) {
        CommandFactory commandFactory = COMMAND_FACTORY_MAP.get(deviceType);
        if (null == commandFactory) {
            throw new BusinessException(Code.ERROR, "暂不支持设备类型: " + deviceType);
        }
        return commandFactory;
    }

    @Override
    protected BaseResult<Map<String, String>> doGetProperties(DeviceEntity deviceEntity) {
        List<DevicePropertyEntity> devicePropertyEntities = devicePropertyRepo.findAllByDeviceId(deviceEntity.getId());
        Map<String, String> res = devicePropertyEntities.stream().collect(Collectors.toMap(DevicePropertyEntity::getPropertyName, DevicePropertyEntity::getPropertyValue));
        return BaseResultFactory.produceSuccess(res);
    }

    @Override
    protected BaseResult doPumpStart(DeviceEntity deviceEntity, PumpWorkVO pumpWorkVO) {
        log.info("设备{}，类型为{}，设置的工作时间为{}s，{}s",deviceEntity.getImei(),deviceEntity.getDeviceType()
                ,pumpWorkVO.getFirstPumpWorkTime(),pumpWorkVO.getSecondPumpWorkTime());
        PumpFrame frame = PumpCommandFactory.startPump(deviceEntity.getDeviceType(),pumpWorkVO);
        // 统计消耗的液体的重量
        List<Integer> capacities = PumpCommandFactory.costMoney(deviceEntity.getDeviceType(),pumpWorkVO);
        log.info("液体泵启动命令执行{}",frame.toString());
        boolean send = aliyunIotMsgSender.send(deviceEntity.getDeviceType(), deviceEntity.getAliyunProdKey(), deviceEntity.getImei(), frame);
        if(send){
            // 更新洗衣液/消毒液 剩余量
            replenishService.updateStockCapacity(deviceEntity.getImei(),capacities);
            ConsumeSizeDTO consumeSizeDTO = new ConsumeSizeDTO();
            consumeSizeDTO.setLSize(capacities.get(0)).setDSize(capacities.get(1));
            addInvokeHistory("operate", consumeSizeDTO, pumpWorkVO.getOrderId(), String.valueOf(send), send, "pumpStart", deviceEntity.getId());
        }

        return BaseResultFactory.produceResult(Code.SUCCESS, this.mockSuccess());
    }

}
